// Created: Dec 14, 2014
//
// Author: wjinwen.1988@gmail.com

#include "chi/net/socket.h"

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <strings.h>

#include <glog/logging.h>

#include "chi/net/inet_address.h"

namespace chi {
namespace net {

Socket::Socket(const int fd) : fd_(fd) { }

Socket::~Socket() { ::close(fd_); }

bool Socket::BindAddress(const InetAddress& addr) {
  int ret = ::bind(fd_, (struct sockaddr*)&addr, 
                   static_cast<socklen_t>(sizeof(addr)));
  return ret == 0 ? true : false; 
}

bool Socket::Listen() {
  int ret = ::listen(fd_, SOMAXCONN);
  return ret == 0 ? true : false;
}

int Socket::Accept(InetAddress* peeraddr) {
  struct sockaddr_in addr;
  socklen_t addrlen = static_cast<socklen_t>(sizeof(addr));
  int connfd = ::accept(fd_, sockaddr_cast(&addr), &addrlen);
  if (connfd < 0) {
    int saved_errno = errno;
    switch (saved_errno) {
      case EAGAIN:
      case ECONNABORTED:
      case EINTR:
      case EPROTO:
      case EPERM:
      case EMFILE:
        errno = saved_errno;
        break;
      case EBADF:
      case EFAULT:
      case EINVAL:
      case ENFILE:
      case ENOBUFS:
      case ENOMEM:
      case ENOTSOCK:
      case EOPNOTSUPP:
        LOG(ERROR) << "unexpected error of ::accpet " << saved_errno;
        break;
      default:
        LOG(ERROR) << "unknown error of ::accept " << saved_errno;
        break;
    }
  }
  peeraddr->set_addr(addr);
  return connfd;
}

void Socket::ShutdownWrite() {
  ::shutdown(fd_, SHUT_WR);
}

void Socket::SetKeepAlive(bool on) {
  int optval = on ? 1 : 0;
  ::setsockopt(fd_, SOL_SOCKET, SO_KEEPALIVE, 
               &optval, static_cast<socklen_t>(sizeof(optval)));
}

void Socket::SetReuseAddress(bool on) {
  int optval = on ? 1 : 0;
  ::setsockopt(fd_, SOL_SOCKET, SO_REUSEADDR,
               &optval, static_cast<socklen_t>(sizeof(optval)));
}

void Socket::SetTcpNoDelay(bool nodelay) {
  int optval = nodelay ? 1 : 0;
  ::setsockopt(fd_, IPPROTO_TCP, TCP_NODELAY,
               &optval, static_cast<socklen_t>(sizeof(optval)));
}

void Socket::SetSoBlocking(bool on) {
  int flags = ::fcntl(fd_, F_GETFL, NULL);
  if (flags >= 0) {
    if (on) {
      flags &= ~O_NONBLOCK; // clear nonblocking
    } else {
      flags |= O_NONBLOCK;  // set nonblocking
    }
    ::fcntl(fd_, F_SETFL, flags); 
  }
}

int Socket::CreateSocketCloseOnExec() {
  int sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (sockfd < 0) {
    LOG(ERROR) << "error in socket()";
  }
  
  int flags = ::fcntl(sockfd, F_GETFL, 0);
  flags |= FD_CLOEXEC;
  ::fcntl(sockfd, F_SETFD, flags);
  
  return sockfd;
}

int Socket::CreateSocketNonblockingOrDie() {
  int sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (sockfd < 0) {
    LOG(ERROR) << "error in socket()";
  }

  int flags = ::fcntl(sockfd, F_GETFL, 0);
  flags |= FD_CLOEXEC;
  ::fcntl(sockfd, F_SETFD, flags);

  flags = ::fcntl(sockfd, F_GETFL, 0);
  flags |= O_NONBLOCK;
  ::fcntl(sockfd, F_SETFL, flags);

  return sockfd;
}

struct sockaddr_in Socket::GetLocalAddr(int sockfd) {
  struct sockaddr_in localaddr;
  bzero(&localaddr, sizeof(localaddr));
  socklen_t addrlen = static_cast<socklen_t>(sizeof(localaddr));
  if (::getsockname(sockfd, sockaddr_cast(&localaddr), &addrlen) < 0) {
    LOG(ERROR) << "error in getsockname()";
  }
  return localaddr;
}

struct sockaddr_in Socket::GetPeerAddr(int sockfd) {
  struct sockaddr_in peeraddr;
  bzero(&peeraddr, sizeof(peeraddr));
  socklen_t addrlen = static_cast<socklen_t>(sizeof(peeraddr));
  if (::getpeername(sockfd, sockaddr_cast(&peeraddr), &addrlen) < 0) {
    LOG(ERROR) << "error in getpeername";
  }
  return peeraddr;
}

} 
}
